<#
Copyright 2012 Chris Duck

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
#>

#ToDo: check to make sure this worked
[Reflection.Assembly]::LoadWithPartialName("WindowsBase") > $null

function GetLocalPackage {
	param(
		[IO.FileInfo]$ModuleRepository = (GetLocalRepository),
		$Id
	)
	Write-Debug "GetLocalPackage: Searching $($ModuleRepository.FullName) for package '$Id'"
	dir $ModuleRepository | ?{$_.PSIsContainer} | ?{
		#If ID was specified, only pass the folder if the name matches
		if($Id) {
			$_.Name -eq $Id
		} else {
			$true
		}
	} | %{
		Write-Debug "GetLocalPackage: Found package '$($_.fullname)'"
		$NuPkgFile = dir $_.Fullname -filter "*.nupkg" | Sort CreationTime -Descending | Select -First 1
		if($NuPkgFile) {
			try {
				Write-Debug "GetLocalPackage: Opening nupkg '$($NuPkgFile.Fullname)'"
				OpenLocalNupkg -NuPkgPath $NuPkgFile
			} catch [System.IO.FileNotFoundException] {
				#If ID was specified, then we want to warn if the folder exists but isn't a package
				if($Id) {
					Write-Warning "NuPkg file not found at '$NuPkgPath', '$Id' exists but is not a valid NuGet package."
				} #Else ignore an unmanaged module
			} catch {
				throw New-Object Exception("Error opening package file $($NuPkgFile.Fullname)")
			}
		} else {
			#If ID was specified, then we want to warn if the folder exists but isn't a package
			if($Id) {
				Write-Warning "NuPkg file not found at '$NuPkgPath', '$Id' exists but is not a valid NuGet package."
			} #Else ignore an unmanaged module
		}
	}
}

function OpenLocalNupkg {
	param(
		[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
		[ValidateNotNullOrEmpty()]
		[IO.FileInfo]$NuPkgPath,
		[Parameter(Mandatory=$false)]
		[switch]$ReadWrite
	)
	process {
		if( $NuPkgPath.Exists ) {
			#Open the existing package
			if($ReadWrite) {
				$FileAccess = [System.IO.FileAccess]::ReadWrite
			} else {
				$FileAccess = [System.IO.FileAccess]::Read
			}
			return [System.IO.Packaging.Package]::Open($NuPkgPath.FullName, [System.IO.FileMode]::Open, $FileAccess)
		} else {
			throw New-Object IO.FileNotFoundException("Unable to locate NuPkg file", $NuPkgPath.FullName)
		}
	}
}

function GetPackageNuSpec {
	param(
		[System.IO.Packaging.Package]$NuPkg
	)
	$Manifests = $NuPkg.GetRelationshipsByType("http://schemas.microsoft.com/packaging/2010/07/manifest")
	if($Manifests) {
		$Manifests | Select-Object -First 1 | %{
			$NuSpecPart = $NuPkg.GetPart($_.TargetUri)
			$NuSpecXml = New-Object Xml.XmlDocument
			try {
				$NuSpecXml.Load($NuSpecPart.GetStream())
			} catch {
				throw New-Object Exception("Error reading NuSpec file with id $($NuPkg.PackageProperties.Identifier)")
			}
			return $NuSpecXml
		}
	} else {
		throw New-Object Exception("File does not seem to contain a nuspec")
	}
}

function ExtractPart {
	param(
		[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
		[ValidateNotNullOrEmpty()]
		[System.IO.Packaging.PackagePart]$Part,
		
		[Parameter(Mandatory=$true)]
		[ValidateNotNullOrEmpty()]
		[IO.DirectoryInfo]$BaseFolder,
		
		[Parameter(Mandatory=$true)]
		[ValidateNotNullOrEmpty()]
		[string]$UriBasePart
	)
	process {
		$PartFilteredUri = $Part.Uri.ToString().Substring($UriBasePart.Length)
		$PartFileStream = $null
		$PartStream = $null
		$PartFilePath = [io.FileInfo](Join-Path $BaseFolder $PartFilteredUri)
		$PartFileParent = Split-Path $PartFilePath.FullName
		if(!$PartFileParent.Exists) {
			mkdir $PartFileParent -Force | Out-Null
		}
		try {
			Write-Debug "ExplodePackage: Exploding part $($Part.Uri) to $($PartfilePath.FullName)"
			$PartFileStream = $PartFilePath.OpenWrite()
			$PartStream = $_.GetStream([IO.FileMode]::Open, [IO.FileAccess]::Read)
			$buffer = New-Object Byte[](4096)
			do {
				$BytesRead = $PartStream.Read($buffer, 0, $buffer.Length)
				$PartFileStream.Write($buffer, 0, $BytesRead)
			} while($BytesRead -gt 0)
		} catch {
			throw New-Object Exception("Error extracting $($Part.Uri) to $($PartFilePath.Fullname): $_", $_.Exception)
		} finally {
			if($PartStream) {
				$PartStream.Close()
			}
			if($PartFileStream)	{
				$PartFileStream.Close()
			}
		}
	}
}

function ExplodePackage {
	param(
		[Parameter(Mandatory=$true)]
		[ValidateNotNullOrEmpty()]
		[System.IO.Packaging.Package]$NuPkg,
		
		[Parameter(Mandatory=$true)]
		[ValidateNotNullOrEmpty()]
		[IO.DirectoryInfo]$ModulePath,
		
		[Parameter(Mandatory=$true)]
		[ValidateNotNullOrEmpty()]
		[IO.DirectoryInfo]$ToolsPath
	)
	$Parts = @($NuPkg.GetParts() | ?{$_.ContentType -ne "application/vnd.openxmlformats-package.relationships+xml"})
	$InternalPartUris = @($NuPkg.GetRelationships() | ?{$_.TargetMode -eq [System.IO.Packaging.TargetMode]::Internal} | Select -ExpandProperty TargetUri)
	
	$ToolsParts = @($Parts | ?{$InternalPartUris -notcontains $_.Uri -and $_.Uri -like "/tools/*"})
	$ContentParts = @($Parts | ?{$InternalPartUris -notcontains $_.Uri -and $_.Uri -like "/content/*"})
	
	if($ToolsParts.count -gt 0) {
		$ToolsParts | ExtractPart -BaseFolder $ToolsPath -UriBasePart "/tools"
	}
	if($ContentParts.count -gt 0) {
		$ContentParts | ExtractPart -BaseFolder $ModulePath -UriBasepart "/content"
	}
}